﻿#region Header
// ------------------------ Licence / Copyright ------------------------
// 
// ScrumTable for Scrum Meeting Support
// Copyright © HSR - Hochschule für Technik Rapperswil
// All Rights Reserved
// 
// Author:
//  Michael Gfeller Silvan Gehrig Patrick Boos
// 
// ---------------------------------------------------------------------
#endregion

#region Usings

using System;
using System.Collections.Generic;

using ScrumTable.Common;
using ScrumTable.Common.Logging;
using ScrumTable.Common.Monitoring;
using ScrumTable.Common.Threading;

#endregion

namespace ScrumTable.DL.Data.TFS.Bulk.Services
{
    /// <summary>
    /// This class is used to load data in an asynchnonous manner.
    /// </summary>
    internal class AsyncDataLoader<T>
    {
        #region Declarations
        //--------------------------------------------------------------------
        // Declarations
        //--------------------------------------------------------------------

        private const int ThreadsPerCpu = 8;

        private readonly ThreadedMessageQueue<AsyncDataBucket> _messageQueue;
        private readonly object _syncRoot;
        private readonly SaveImportMonitor _importMonitor;
        private readonly string _importMonitorId;
        private readonly AsyncCallback _callback;

        private int _currentImportState;
        private int _maxImportState;
        private volatile Exception _error;

        #endregion

        #region Properties
        //--------------------------------------------------------------------
        // Properties
        //--------------------------------------------------------------------

        private bool HasError
        {
            get { return (_error != null); }
        }

        #endregion

        #region Constructors / Destructor
        //--------------------------------------------------------------------
        // Constructors / Destructor
        //--------------------------------------------------------------------

        /// <summary>
        /// Creates a new AsyncDataLoader instance, no public constructor
        /// needed.
        /// </summary>
        internal AsyncDataLoader(object syncRoot, IDataImportMonitor importMonitor, string importMonitorId, AsyncCallback callback)
        {
            PreCondition.AssertNotNull(syncRoot, "syncRoot");
            PreCondition.AssertNotNull(importMonitor, "importMonitor");
            PreCondition.AssertNotNull(importMonitorId, "importMonitorId");

            _messageQueue = new ThreadedMessageQueue<AsyncDataBucket>(Convert.ToUInt32(Environment.ProcessorCount * ThreadsPerCpu));
            _syncRoot = syncRoot;
            _importMonitor = new SaveImportMonitor(importMonitor);
            _importMonitorId = importMonitorId;
            _importMonitor.StartSubProgress(_importMonitorId);
            _callback = callback;
        }

        #endregion

        #region Methods
        //--------------------------------------------------------------------
        // Methods
        //--------------------------------------------------------------------

        /// <summary>
        /// The load asynchronous method loops through all elements in the list
        /// to load and executes first the asychronous part and then the 
        /// context synchronized part.
        /// </summary>
        /// <param name="toLoad">Specifies the tokens with the information about the data to load.</param>
        /// <param name="asyncPart">Specifies the asynchronous part, should contain long running process.</param>
        /// <param name="contextSyncPart">Specifies the context synchronized part. This part should only contain an import/assignment.</param>
        internal void LoadAsync(ICollection<T> toLoad, Action<T> asyncPart, Action<T> contextSyncPart)
        {
            PreCondition.AssertNotNull(toLoad, "toLoad");
            PreCondition.AssertNotNull(asyncPart, "asyncPart");
            PreCondition.AssertNotNull(contextSyncPart, "contextSyncPart");

            _currentImportState = 0;            // must be synchronized, set in each call, sycn point is _context
            _maxImportState = toLoad.Count;     // must not be synchronized, only set once at start
            
            if (_maxImportState != 0)
            {
                foreach (T item in toLoad)
                {
                    _messageQueue.Enqueue(
                        new MessageQueueCall<AsyncDataBucket>(LoadByThreadPoolThread),
                        new AsyncDataBucket
                            {
                                DataItem = item,
                                AsyncPart = asyncPart,
                                ContextSyncPart = contextSyncPart
                            });
                }
            }
            else
            {
                Finish(null);
            }
        }

        private void LoadByThreadPoolThread(AsyncDataBucket data)
        {
            int currentImportState = 0;

            CallAsyncPart(data.AsyncPart, data.DataItem);

            lock (_syncRoot)
            {
                CallSyncPart(data.ContextSyncPart, data.DataItem);
                currentImportState = ++_currentImportState;
            }

            ReportProgress(currentImportState);

            if (currentImportState == _maxImportState)
            {
                Finish(HasError ? new AsyncDataLoadException(_error) : null);
            }
        }

        private void ReportProgress(int currentImportState)
        {
            _importMonitor.ReportProgress(_importMonitorId, currentImportState, _maxImportState);
        }

        private void CallAsyncPart(Action<T> asyncPart, T data)
        {
            try
            {
                if (!HasError)
                {
                    asyncPart(data);
                }
            }
            catch (Exception e)
            {
                Logger<AsyncDataLoader<T>>.Info(e);
                _error = e;
            }
        }

        private void CallSyncPart(Action<T> contextSyncPart, T data)
        {
            try
            {
                if (!HasError)
                {
                    contextSyncPart(data);
                }
            }
            catch (Exception e)
            {
                Logger<AsyncDataLoader<T>>.Info(e);
                _error = e;
            }
        }

        private void Finish(Exception error)
        {
            _importMonitor.EndSubProgress(_importMonitorId, error);
            _messageQueue.Abort();

            if (_callback != null)
                _callback(new DataResult(error));
        }

        #endregion

        #region Events
        //--------------------------------------------------------------------
        // Events
        //--------------------------------------------------------------------

        #endregion


        //--------------------------------------------------------------------
        // Nested Classes
        //--------------------------------------------------------------------

        /// <summary>
        /// This class contains a set of references which are used to 
        /// perform asynchronous operations.
        /// </summary>
        private class AsyncDataBucket
        {
            #region Properties
            //--------------------------------------------------------------------
            // Properties
            //--------------------------------------------------------------------

            internal T DataItem { get; set; }
            internal Action<T> AsyncPart { get; set; }
            internal Action<T> ContextSyncPart { get; set; }

            #endregion
        }
    }
}